昨天我們提到,一張圖片是由許多 Pixel 所組成,而每個 Pixel 都會記錄一些數值。
不過,當時還留下一個問題。
如果是灰階圖片,一個 Pixel 只需要記錄一個亮度值就可以了。
那如果今天是一張彩色圖片呢?
例如有張照片裡面有一片藍天、一棵綠樹、一朵紅花。
電腦又是怎麼把這些顏色記錄下來的?
其實電腦並不是直接記住「紅色」、「藍色」這些顏色,而是透過不同的色彩空間(Color Space)來表示它們。
今天想要和大家介紹三個最常看到的色彩空間:
RGB 應該是最常看到的色彩空間。
它分別代表:
每個 Pixel 都會記錄這三個通道的數值。
例如:
R = 255
G = 0
B = 0
代表純紅色。
而:
R = 255
G = 255
B = 255
代表白色。
如果三個值都是 0:
R = 0
G = 0
B = 0
就是黑色。
也就是說,每個 Pixel 都可以想成:
(R, G, B)
如果每個通道都是 8-bit,就有 2^8=256 種可能。
因此一個 Pixel 可以表示:
256 × 256 × 256 ≈ 1677 萬種顏色
我們可以直接建立一張只有三個顏色的圖片。
import numpy as np
import matplotlib.pyplot as plt
img = np.zeros((200, 600, 3), dtype=np.uint8)
img[:, :200] = [255, 0, 0]
img[:, 200:400] = [0, 255, 0]
img[:, 400:] = [0, 0, 255]
plt.imshow(img)
plt.axis("off")
plt.show()
結果會長這樣:

這張圖片只有三個區塊。
分別是:
也是 RGB 三個基本通道。
有些時候,我們其實不需要顏色。
例如:
很多演算法更在意的是「哪裡比較亮?」
而不是「是什麼顏色?」
這時候就會把圖片轉成 Gray(灰階)。
灰階圖片只有一個通道。
例如:
0
代表黑色。
255
代表白色。
中間則是不同亮度的灰色。
我們可以非常容易的用 OpenCV 完成灰階轉換。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("image.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
plt.imshow(gray, cmap="gray")
plt.axis("off")
plt.show()
結果如下:
轉成灰階之後,圖片的顏色消失了。
留下的只有亮度資訊。
這裡補充一個實作中非常重要的一點。
回到剛剛的程式碼,你應該會看到這段:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
這行是幹嘛的呢?
剛剛我們有提到,全彩影像會用 RGB 表示。
但在 OpenCV 套件中,表示全彩的方式是使用 BGR 來表示,通道的順序不一樣。
因此這行程式碼的意思就是:把 BGR 的通道順序轉回一般的 RGB 順序。
如果不轉會怎樣?
我們來試試看:
準備一張圖片測試:
原圖:
接著執行下面這段程式:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=plt.imread("dog.jpeg")
plt.title("Without OpenCV")
plt.axis("off")
plt.imshow(img)
plt.show()
cv2_img=cv2.imread("dog.jpeg")
plt.title("With OpenCV")
plt.axis("off")
plt.imshow(cv2_img)
plt.show()
來看一下結果:


可以發現色彩失真了。
這就是為什麼要在用 OpenCV 讀入時先進行轉換。
如果沒有轉換,讀入的圖片就不會是原始顏色。
很多人以為灰階就是:
(R + G + B) / 3
也就是三通道的平均值。
但其實不是。
人眼對不同顏色的敏感程度不同。
綠色通常最敏感。
藍色則相對沒那麼敏感。
因此常見的灰階轉換方式會給不同權重。
例如:
Gray = 0.299R + 0.587G + 0.114B
所以同樣亮度的紅色和綠色,轉成灰階之後,結果其實不一定一樣。
RGB 很直覺。
但如果要找某一種顏色,就沒有那麼方便。
例如我要找「紅色」。
RGB 到底是多少?
(255,0,0)?
(230,20,20)?
(180,40,50)?
可能有很多種。
因此很多影像處理工作,會改用 HSV。
HSV 分成三個部分:
如果把它想成調色盤:
Hue 決定是什麼顏色。
Saturation 決定顏色鮮不鮮豔。
Value 決定亮不亮。
因此:
改變 Hue,顏色就會改變。
改變 Saturation,顏色會慢慢變淡。
改變 Value,整張圖片亮度會變。
接著,我們把剛剛的 RGB 圖片轉換成 HSV。
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
print(hsv[0, 0])
執行後,可以看到左上角 Pixel 的 HSV 值:
[0 255 255]
先回想一下 HSV 三個通道分別代表什麼:
因為左上角 Pixel 是純紅色,所以飽和度與明度都是最大值 255,這不難理解。
比較容易讓人疑惑的是第一個數值 0。
為什麼紅色是 0?
其實在 HSV 中,顏色並不是由三個通道混合而成,而是用「色相」來表示顏色的位置。
可以把 Hue 想像成一個色相環,不同的角度代表不同的顏色。
紅色剛好位於起點,因此 Hue 會是 0。
不過,如果你查其他資料,可能會發現有些地方會把 Hue 表示成 0 到 360 度。
這是因為 HSV 原本就是以角度來描述顏色。
而 OpenCV 為了方便使用 8-bit 影像儲存資料,將 Hue 的範圍縮放成 0 到 179,因此在 OpenCV 中看到的數值,和部分教科書或其他工具可能會有些不同。
雖然 HSV 的數值沒有 RGB 那麼直覺,但它在影像處理中卻非常實用。
例如想找出畫面中的某種顏色時,比起一次要同時限制 R、G、B 三個通道,直接限制 Hue 的範圍通常會比較容易。
所以像是:
都很常先把圖片轉換成 HSV,再進行後續的處理。
今天整理了三種最常見的色彩空間。
目前看起來,RGB、Gray、HSV 好像只是不同的表示方式。
不過下一篇開始,我們會發現,當圖片進入電腦之後,除了顏色之外,還有另外一件同樣重要的事情。
那就是圖片是如何被數位化的。
也就是 Sampling(取樣)與 Quantization(量化)。